package hafnium.bukkit.util;

import java.io.Serializable;

import org.bukkit.Bukkit;
import org.bukkit.Chunk;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.block.Block;

/**
 * This is an alternative to bukkit's location class that uses (usually) less
 * memory and can survive the unloading of a world.
 * 
 * BlockLocation instances are immutable.
 * 
 * @author Chris
 * 
 */
public class BlockLocation implements Serializable {
	private static final long serialVersionUID = 1L;

	private final String worldName;
	private final int x, y, z;

	public BlockLocation(Location l) {
		this(l.getWorld(), l.getBlockX(), l.getBlockY(), l.getBlockZ());
	}

	public BlockLocation(World world, int x, int y, int z) {
		this(world.getName(), x, y, z);
	}

	public BlockLocation(String worldName, int x, int y, int z) {
		this.worldName = worldName;
		this.x = x;
		this.y = y;
		this.z = z;
	}

	/**
	 * Get the world the block is in.
	 * 
	 * @return
	 */
	public World getWorld() {
		return Bukkit.getServer().getWorld(this.getWorldName());
	}

	/**
	 * Gets the name of the world this block is in.
	 * 
	 * @return
	 */
	public String getWorldName() {
		return this.worldName;
	}

	/**
	 * Get the block X coordinate for the block.
	 * 
	 * @return
	 */
	public int getX() {
		return x;
	}

	/**
	 * Get the block Y coordinate for the block.
	 * 
	 * @return
	 */
	public int getY() {
		return y;
	}

	/**
	 * Get the block Z coordinate for the block.
	 * 
	 * @return
	 */
	public int getZ() {
		return z;
	}

	public Chunk getChunk() {
		return this.getWorld().getChunkAt(this.getX(), this.getZ());
	}

	public Block getBlock() {
		return this.getWorld().getBlockAt(this.getX(), this.getY(), this.getZ());
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Object#hashCode()
	 */
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((worldName == null) ? 0 : worldName.hashCode());
		result = prime * result + x;
		result = prime * result + y;
		result = prime * result + z;
		return result;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (!(obj instanceof BlockLocation))
			return false;
		BlockLocation other = (BlockLocation) obj;
		if (worldName == null) {
			if (other.worldName != null)
				return false;
		} else if (!worldName.equals(other.worldName))
			return false;
		if (x != other.x)
			return false;
		if (y != other.y)
			return false;
		if (z != other.z)
			return false;
		return true;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		return this.worldName + "@" + toWINT(x) + "." + toWINT(y) + "." + toWINT(z);
	}

	private static String toWINT(int val) {
		return (val < 0 ? "n" : "") + (int) Math.abs(val);
	}

	/**
	 * 
	 * @param block
	 *            The block to check.
	 * @return true if this BlockLocation defines the specified block.
	 */
	public boolean locates(Block block) {
		return this.locates(block.getLocation());
	}

	/**
	 * 
	 * @param loc
	 *            The location to check.
	 * @return true if this BlockLocation defines the block that contains the
	 *         specified location.
	 */
	public boolean locates(Location loc) {
		return this.getWorldName().equals(loc.getWorld().getName()) && this.getX() == loc.getBlockX() && this.getY() == loc.getBlockY() && this.getZ() == loc.getBlockZ();
	}
}
